#ifndef ZORAL_FILE_H
#define ZORAL_FILE_H

#include <Eigen/Dense>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>

bool EnsureDirExists(const std::string &filename);
std::string JoinPath(const std::string &path);
template<typename... Args>
std::string JoinPath(const std::string &first, const Args &...args)
{
    std::string separator = "/";
    std::string rest = JoinPath(args...);

    if (first.empty())
    {
        return rest;
    }

    if (rest.empty())
    {
        return first;
    }

    if (first.back() == '/' || first.back() == '\\')
    {
        separator = "";
    }
    else if (rest.front() == '/' || rest.front() == '\\')
    {
        separator = "";
    }

    return first + separator + rest;
}

template<typename T>
int WriteData2D(const std::string &filename, const std::vector<T> &data, char delimiter = '\n', int precision = 8)
{
    if (!EnsureDirExists(filename))
        return -1;
    std::ofstream file(filename);
    if (!file.is_open())
        return -1;

    file << std::fixed << std::setprecision(precision);
    for (const auto &val: data)
        file << val << delimiter;
    file.close();
    return file.good() ? 0 : -1;
}

// 写 std::vector<std::vector<T>>
template<typename T>
int WriteData2D(const std::string &filename, const std::vector<std::vector<T>> &data, char delimiter = ',',
              int precision = 8)
{
    if (!EnsureDirExists(filename))
        return -1;
    std::ofstream file(filename);
    if (!file.is_open())
        return -1;

    file << std::fixed << std::setprecision(precision);
    for (const auto &row: data)
    {
        for (size_t i = 0; i < row.size(); ++i)
        {
            file << row[i];
            if (i + 1 < row.size())
                file << delimiter;
        }
        file << '\n';
    }
    file.close();
    return file.good() ? 0 : -1;
}

// 写 Eigen::VectorXd
inline int WriteData1D(const std::string &filename, const std::vector<double> &vec, char delimiter = '\n',
                       int precision = 8)
{
    // std::vector<double> tmp(vec.data(), vec.data() + vec.size());
    return WriteData2D(filename, vec, delimiter, precision);
}

// 写 Eigen::VectorXd
inline int WriteData1D(const std::string &filename, const Eigen::VectorXd &vec, char delimiter = '\n',
                       int precision = 8)
{
    std::vector<double> tmp(vec.data(), vec.data() + vec.size());
    return WriteData2D(filename, tmp, delimiter, precision);
}

// 写 Eigen::MatrixXd
inline int WriteData2D(const std::string &filename, const Eigen::MatrixXd &mat, char delimiter = ',', int precision = 8)
{
    if (!EnsureDirExists(filename))
        return -1;
    std::ofstream file(filename);
    if (!file.is_open())
        return -1;

    file << std::fixed << std::setprecision(precision);
    for (int i = 0; i < mat.rows(); ++i)
    {
        for (int j = 0; j < mat.cols(); ++j)
        {
            file << mat(i, j);
            if (j + 1 < mat.cols())
                file << delimiter;
        }
        file << '\n';
    }
    file.close();
    return file.good() ? 0 : -1;
}

// ---------- 读取统一接口 ----------

// 读 std::vector<std::vector<T>>
template<typename T = double>
std::vector<std::vector<T>> ReadData2D(const std::string &filename, char delimiter = ',')
{
    std::vector<std::vector<T>> result;
    std::ifstream file(filename);
    if (!file.is_open())
        return result;

    std::string line;
    while (std::getline(file, line))
    {
        std::stringstream ss(line);
        std::string field;
        std::vector<T> row;
        while (std::getline(ss, field, delimiter))
        {
            if (std::is_same_v<T, double>)
            {
                row.push_back(std::stod(field));
            }
            else if (std::is_same_v<T, float>)
            {
                row.push_back(std::stof(field));
            }
//            else
//            {
//                static_assert(std::is_same<T, void>::value, "Unsupported type");
//            }
        }
        result.push_back(std::move(row));
    }
    return result;
}

template <typename T = double>
std::vector<T> ReadData1D(const std::string& filename, char delimiter = ',') {
    auto mat = ReadData2D<T>(filename, delimiter);
    if (mat.empty()) return {};

    std::vector<T> data;
    data.reserve(mat.size());

    for (const auto& row : mat) {
        if (!row.empty()) {
            data.push_back(row[0]);  // 取第0列
        }
    }
    return data;
}

std::string GetCurrentTimeString();

#endif // ZORAL_FILE_H
